<?php

namespace Zeke\Net\Http\Middleware;

use Zeke\Net\Http\Parser\IniUtil;
use RingCentral\Psr7\BufferStream;
use Psr\Http\Message\ServerRequestInterface;
use Zeke\Contracts\Io\Stream\ReadableStreamContracts;
use Zeke\Zee\Event\BufferEvent;
use Zeke\Zee\Promise\Promise;


/**
 * 获取http请求的body数据中间件
 *
 * Class RequestBodyBufferMiddleware
 * @package Zeke\Net\Http\Middleware
 */
final class RequestBodyBufferMiddleware
{

    /**
     * body最大字节数
     * @var int
     */
    private $sizeLimit;

    /**
     * @param int|string|null $sizeLimit Either an int with the max request body size
     *                                   in bytes or an ini like size string
     *                                   or null to use post_max_size from PHP's
     *                                   configuration. (Note that the value from
     *                                   the CLI configuration will be used.)
     * 使用PHP配置中的post_max_size既可以是具有以字节为单位的最大请求主体大小的整数，
     * 也可以是类似ini的大小字符串或null。（注意，将使用来自CLI配置的值。）
     * 控制最大post数据量
     */
    public function __construct($sizeLimit = null)
    {
        if ($sizeLimit === null) {
            $sizeLimit = ini_get('post_max_size');
        }

        $this->sizeLimit = IniUtil::iniSizeToBytes($sizeLimit);
    }

    /**
     * 调用此类
     *
     * @param ServerRequestInterface $request
     * @param $stack
     * @return Promise
     * @author <liu.bin@ruaby.com> 2018-08-24 14:09
     */
    public function __invoke(ServerRequestInterface $request, $stack)
    {

        //bodyStream对象
        $bodyStream = $request->getBody();
        //header中content-length定义的size
        $size = $bodyStream->getSize();

        // happy path: skip if body is known to be empty (or is already buffered)
        if ($size === 0 || !$bodyStream instanceof ReadableStreamContracts) {
            // replace with empty body if body is streaming (or buffered size exceeds limit)
            if ($bodyStream instanceof ReadableStreamContracts || $size > $this->sizeLimit) {
                $request = $request->withBody(new BufferStream(0));
            }

            //执行下一级中间件
            return $stack($request);
        }

        // request body of known size exceeding limit
        // $sizeLimit:php.ini定义的http数据报最大值
        $sizeLimit = $this->sizeLimit;
        //content-length长度超过php.ini规定的http报文长度
        if ($size > $this->sizeLimit) {
            $sizeLimit = 0;
        }

        //该buffer只包含body数据
        $buffer = '';

        //定义promise,返回给streamServer,调用then注册$resolve回调函数
        $promise = new Promise(function($resolve, $reject) use(&$buffer,$bodyStream,$sizeLimit,$request,$stack,$size){

            $bodyStream->on(BufferEvent::DATA, function ($data) use (&$buffer, $sizeLimit,$request,$stack,$size,$resolve, $reject) {

                $buffer .= $data;

                // $buffer 超出最大限定
                if ($sizeLimit !== null && isset($buffer[$sizeLimit])) {
                    throw new \OverflowException('Buffer exceeded maximum length');
                }

                //判断http包接收完整
                //todo 需解决粘包的数据问题
                if(strlen($buffer) == $size){
                    //设置body字符串映射成BufferStream对象
                    $stream = new BufferStream(strlen($buffer));
                    $stream->write($buffer);

                    //设置$request的body数据
                    $request = $request->withBody($stream);

                    // 通知上一级中间件，调用上一级promise定义的then方法
                    // $promise引用在LimitConcurrent中间件对象中，

                    // 如果去掉LimitConcurrent中间件，则直接调用StreamServer的response的then方法
                    $resolve([$stack,$request]);
                }

            });


            //注册错误事件
            $bodyStream->on('error', function ($error) {
                throw new \RuntimeException('An error occured on the underlying stream while buffering', 0, $error);
            });

            //注册关闭事件
            $bodyStream->on('close', function () use ( &$buffer) {
                //$resolve($buffer);
            });

        });


        return $promise;

    }


}
